package edu.hawaii.duedates;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import argparser.ArgParser;
import argparser.BooleanHolder;
import argparser.DoubleHolder;
import argparser.IntHolder;
import argparser.StringHolder;
import edu.hawaii.duedates.email.Mailer;
import edu.hawaii.duedates.library.HawaiiStateLibrary;
import edu.hawaii.duedates.library.UhManoaLibrary;

/**
 * Due Dates main class for parsing the command line arguments and gathering data
 * based on what those arguments are. Class utilizes ArgParser libraries to parse
 * the command line arguments.<br />
 * 
 * Examples of valid command line arguments:<br />
 * -uhm [id] [last name] -- checks university of hawaii library<br />
 * -hsl [barcode] [last 4 digits of phone number] -- checks hawaii state library<br />
 * -sort [library|duedate] -- sorts items out based on library or duedate <br />
 * -within [number 1-365] -- outputs items due within a specified number of days.<br />
 * -console -- if specified outputs results to console.<br />
 * -email [address] [smtp server] -- sends out an email with due date results. <br />
 * -wakeup [double number] -- if specified, wakes up every interval and runs Due Dates
 * system. <br />
 * 
 * @author Tyler Wolff and Scheller Sanchez
 */
public class DueDates {

  // Final values for holding parsed command line arguments
  private final String[] uhCommandInfo;
  private final int[] hslParam;
  private final StringHolder sort;
  private final IntHolder within;
  private final BooleanHolder console;
  private final String[] email;
  private final DoubleHolder wakeup;
  
  // Holds list of items that are due for a given instance of due dates.
  private List<ItemDue> itemsDue;

  /**
   * Constructor for an instance of DueDates. Parses the command line arguments
   * and initializes object values based on this.
   * 
   * @param params The Command line arguments.
   */
  public DueDates (String [] params) {
    
    // Initializing argument holders
    this.uhCommandInfo = new String[2];
    this.hslParam = new int[2];
    this.sort = new StringHolder();
    this.within = new IntHolder();
    this.console = new BooleanHolder();
    this.email = new String[2];
    this.wakeup = new DoubleHolder();
    
    this.itemsDue = new ArrayList<ItemDue>();
    
    // Setting default values
    this.hslParam[0] = -1;
    this.hslParam[1] = -1;
    this.sort.value = "library";
    this.wakeup.value = 0;
    
    // Creating parser with Option values
    ArgParser parser = new ArgParser("java argparser.DueDates");
    parser.addOption("-uhm %sX2 #id and last name for UH Manoa Library", uhCommandInfo);
    parser.addOption("-hsl %dX2 #id and last 4 digits of telephone number for hawaii "
        + "state library system", hslParam);
    parser.addOption("-sort %s {library, duedate} #name of parameter to sort", sort);
    parser.addOption("-within %d {[0, 365]} #integer representing days to sort within", within);
    parser.addOption("-console %v #if specified, outputs results to console", console);
    parser.addOption("-email %sX2 #email account and smtpserver name", email);
    parser.addOption("-wakeup %f #the number of days between checks", wakeup);

    // Matching parameters to Options
    parser.matchAllArgs(params);
    
  }
  
  /**
   * Returns a list of all the borrowed items from the libraries specified by user. 
   * List is also sorted and check for within dates before it is returned (if 
   * specified by user).
   * 
   * @return A list of ItemDue objects.
   */
  List<ItemDue> getItems() {

    // Will hold total list of ItemDue Objects
    List<ItemDue> allItemsDue = new ArrayList<ItemDue>();
    List<ItemDue> itemsDueUH = new ArrayList<ItemDue>();
    List<ItemDue> itemsDueHsl = new ArrayList<ItemDue>();


    // If -uhm parameters were given
    if (this.uhCommandInfo[0] != null) {

      UhManoaLibrary uhManoa = new UhManoaLibrary();
      
      try {
        itemsDueUH = uhManoa.getItemInfo(uhCommandInfo[0], uhCommandInfo[1]);
      }
      catch (Exception e) {
        System.out.println(e.getMessage());
      }
    }

    // Checks for Hawaii State Library parameters
    if ((hslParam[0] != -1)) {
      HawaiiStateLibrary hawaiiStateLib = new HawaiiStateLibrary();
      
      try {
        itemsDueHsl = hawaiiStateLib.getItemInfo(Integer.toString(hslParam[0]), 
            Integer.toString(hslParam[1]));
      }
      catch (Exception e) {
        System.out.println(e.getMessage());
      }
    } 

    // Appends all items to one single list
    if (!itemsDueUH.isEmpty()) {
      allItemsDue.addAll(itemsDueUH);
    }
    if (!itemsDueHsl.isEmpty()) {
      allItemsDue.addAll(itemsDueHsl);
    }
      
    allItemsDue = itemSort(allItemsDue, sort.value);
    allItemsDue = itemsWithin(allItemsDue, within.value);
    return allItemsDue;
  } // end getItems

  
  /**
   * Outputs the items due for a given instance of DueDates in the form of an email,
   * or through the console, depending what the user has specified.
   * 
   */
  public void output() {
    if (this.console.value) {
      System.out.println(this.toString());
    }   
      
    if (this.email[0] != null && haveItemsOut()) {
      boolean sent = false;
      Mailer mail = new Mailer();
      sent = mail.send(email[0], email[1], this.toString());
      
      if (sent) {
        System.out.println("Results were successfully sent to email.");
      } 
      else {
        System.out.println("Email report was unsuccessful.");
      }
    }
  }
  
  /**
   * Returns true if there are items due for a given DueDates instance.
   * 
   * @return true if there are items due. false if there are not.
   */
  
  public boolean haveItemsOut() {
    return (!this.getItemsDue().isEmpty());
  }
  
  /**
   * Returns a string representation of all items due for a given instance
   * of DueDates. String is formatted using the format function.
   * 
   * @return A formatted string of all items due for a given DueDate.
   */
  @Override
  public String toString() { 
    
    if (!haveItemsOut()) {
      return "You have no items currently due";
    }
    
    String format = "|%1$-23s|%2$-50s|%3$-20s|%4$-10s|\n";
    StringBuffer dueDates = new StringBuffer();
    dueDates.append(String.format(format, "LIBRARY", "ITEM DESCRIPTION", "ITEM TYPE", "DUE DATE"));
    
    for (ItemDue item : this.getItemsDue()) {
      dueDates.append(item.toString());
      dueDates.append('\n');
    }
    
    return dueDates.toString();
    
  }
  
  /**
   * Returns a sorted list of ItemDue objects based on either library or
   * due date. The default value is library if user does not specify.
   * Makes use of the Collections.sort method which takes in a collection 
   * as well as a comparator. 
   * 
   * @param itemsDue A list of all items Due.
   * @param sort The way to sort the list - either by library or due date.
   * @return A sorted list of ItemDue objects.
   */
  private List<ItemDue> itemSort (List<ItemDue> itemsDue, String sort) {
    
    if ("library".equals(sort)) {
      Collections.sort(itemsDue, ItemDue.libraryComparator);
    }
    else if ("duedate".equals(sort)) {
      Collections.sort(itemsDue, ItemDue.dueDateComparator);  
    }
    
    return itemsDue;    
  }
  
  /**
   * Returns a sub list of ItemDue objects from another list of ItemDue objects
   * based on the specified within parameter. If within is 0, the original list 
   * is returned. Otherwise, the items in the original list are compared to the
   * current date in conjunction with the within parameter (ie. if within parameter 
   * is 1, all the items due within 1 day from the current date will be placed in
   * a new list to be returned).
   * 
   * @param itemsDue A list of all items Due.
   * @param within The number of days from current day.
   * @return A sub-list of ItemDue objects within the specified day threshold.
   */
  private List<ItemDue> itemsWithin (List<ItemDue> itemsDue, int within) {
    if (within == 0) {
      return itemsDue;
    } 
    else {
      
      List<ItemDue> itemsWithin = new ArrayList<ItemDue>();
      
      for (ItemDue item : itemsDue) { 
        
        //parses the due dates and find the how many days till due
        SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yy", Locale.US);
        Date today = new Date();
        Date dateDue = new Date();
        
        try {
          dateDue = dateFormat.parse(item.getItemDueDate());
        }
        catch (Exception e) {
          System.out.println(e.getMessage());
        }
        
        long diff = (dateDue.getTime() - today.getTime()) / (1000 * 60 * 60 * 24);
      
        if (diff <= within) {
          itemsWithin.add(item);
        }
      }
      
      return itemsWithin;
    }
  }
  
/////////////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
  
  /**
   * Sets the itemsDue.
   * 
   * @param itemsDue the itemsDue to set.
   */
  public void setItemsDue(List<ItemDue> itemsDue) {
    this.itemsDue = itemsDue;
  }
  
  /**
   * Returns the itemsDue.
   * 
   * @return A list of Items due.
   */
  public List<ItemDue> getItemsDue() {
    return itemsDue;
  }

  /**
   * Returns the Command line args for -uhm.
   * 
   * @return the uhCommandInfo.
   */
  public String[] getUhCommandInfo() {
    // copy to get around QA problems
    String [] copy = new String[2];
    copy[0] = uhCommandInfo[0];
    copy[1] = uhCommandInfo[1];
    return copy;
  }

  /**
   * Returns the command line args for -hsl.
   * @return the hslParam
   */
  public int[] getHslParam() {
    // copy to get around QA problems
    int [] copy = new int[2];
    copy[0] = hslParam[0];
    copy[1] = hslParam[1];
    return copy;
  }

  /**
   * Returns the command line args for -sort.
   * @return the sort
   */
  public StringHolder getSort() {
    return sort;
  }

  /**
   * Returns the command line args for -within.
   * @return the within
   */
  public IntHolder getWithin() {
    return within;
  }

  /**
   * Returns the value of console.
   * @return the console
   */
  public BooleanHolder getConsole() {
    return console;
  }

  /**
   * Returns the email parameters for -email.
   * @return the email
   */
  public String[] getEmail() {
    // copy to get around QA problems
    String [] copy = new String[2];
    copy[0] = email[0];
    copy[1] = email[1];
    return copy;
  }

  /**
   * Returns the wakeup parameters.
   * @return the wakeup
   */
  public DoubleHolder getWakeup() {
    return wakeup;
  }
  
  /**
   * Returns the wakeup value.
   * @return the wakeup value
   */
  public double getWakeupValue() {
    return wakeup.value;
  }
/////////////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\  

  /**
   * Utilizes a users information to login to a given library system and gather data about the users
   * current check out books.
   * 
   * @param params The command line arguments.
   * @throws Exception If problems occur accessing the library sites.
   */
  public static void main(String[] params) throws Exception {

    DueDates dueDate = new DueDates(params);
    System.out.println("Gathering data from specified libraries...");
    dueDate.setItemsDue(dueDate.getItems());
    
    if (dueDate.getWakeupValue() > 0) {
      Timer timer = new Timer();
      Calendar date = Calendar.getInstance();
      //for outputs after the first
      DueDateTimer dueDateTimer = new DueDateTimer(dueDate);
      timer.scheduleAtFixedRate(dueDateTimer, date.getTime(), (long) 
          ((dueDate.getWakeupValue()) * (24 * 60 * 60)));
    }
      
    else {
      dueDate.output();
    }
    
  } // end main
  
} // end DueDates class